﻿using System;
using System.Net;
using System.Linq;
using System.Threading;
using System.Collections.Generic;
///
using ScheduleMVVM.Model.Miscellaneous;
using ScheduleMVVM.Model.PersistentFramework;


namespace ScheduleMVVM.Model.RuntimeFramework
{
    public class DataAccess
    {
        #region Public members
        /// <summary>
        /// Route trains collection
        /// </summary>
        public DataItem[] Result
        {
            get
            {
                /// Check for existing cache            
                if (cache.DataPresent && !CacheExpired())
                { // Cache present
                    return cache.Value.ToArray();
                }
                else
                { // No cache, downloading
                    Refresh();
                    return null;
                }
            }
        }
        /// <summary>
        /// Source of 'Result' property data
        /// </summary>
        public DataSource Source
        {
            get
            {
                if (cache.DataPresent && !CacheExpired()) return DataSource.CACHE;
                else return DataSource.WEB;
            }
        }
        /// <summary>
        /// Acqusition time of 'Result' property data
        /// </summary>
        public DateTime SourceTimeStamp
        {
            get
            {
                if (cache.DataPresent && !CacheExpired())
                    return cache.DataAcquireTime;
                else return new DateTime(1, 1, 1);
            }
        }
        /// <summary>
        /// Forces data refresh, async
        /// </summary>
        public void Refresh()
        {
            Refresh(true);
        }
        /// <summary>
        /// Forces data refresh
        /// </summary>
        /// <param name="async">Determines whether data acquisition should be done asynchroniously</param>
        public void Refresh(Boolean async)
        {
            if (primaryWebClient == null) ResetConnection();
            if (!busy)
            {
                primaryWebClient.DownloadStringAsync(new Uri(Settings.Current.CurrentEngine.GetQueryString(parentFavourite.startStation, parentFavourite.endStation), UriKind.Absolute));
                busy = true;
                NotifyInvoked();
                if (!async)
                {
                    if (synchronizeLock == null) synchronizeLock = new AutoResetEvent(false);
                    sync_mode = true;
                    synchronizeLock.WaitOne();
                }
            }            
        }
        /// <summary>
        /// An array of next train departure times (up to 4) in string format. Returns empty string if there's no data present
        /// </summary>
        public String NextUp
        {
            get
            {
                String backContent = String.Empty;
                Int32 count = 0;
                if (Result != null) foreach (var i in Result)
                    {
                        if (i.Departure > DateTime.Now)
                        {
                            backContent += i.Departure.ToString("t");
                            backContent += "\n";
                            count++;
                        }
                        if (count == 4) break;
                    }
                else backContent = String.Empty;
                return backContent;
            }
        }
        public void Cancel()
        {
            if(primaryWebClient != null)
                primaryWebClient.CancelAsync();
        }
        #endregion
        #region Private fields and logic
        protected WebClient primaryWebClient;
        protected String primaryWebClientResult;
        protected Boolean busy;

        protected RouteEntry parentFavourite;

        protected SyncableXml<List<DataItem>> cache;
        protected Thread dataHandlerThread;
        private AutoResetEvent synchronizeLock;
        private Boolean sync_mode;

        protected void ResetConnection()
        {
            primaryWebClient = new WebClient();
            primaryWebClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(primaryWebClient_DownloadStringCompleted);
        }
        void primaryWebClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                NotifyError("Download has been cancelled", null, ErrorCause.NO_ERROR);
                CheckReleaseEvent();
                return;
            }
            if (e.Error != null)
            {
                NotifyError("Error during resource downloading", e.Error, ErrorCause.DOWNLOAD_ERROR);
                CheckReleaseEvent();
                return;
            }
            /// Everything's fine, continue
            primaryWebClientResult = e.Result;
            /// Launch new thread
            if (sync_mode)
            {
                ProcessDownloadedData();
            }
            else
            {
                if (dataHandlerThread == null) dataHandlerThread = new Thread(new ThreadStart(ProcessDownloadedData));
                try
                {
                    dataHandlerThread.Start();
                }
                catch (ThreadStateException) // The thread has already been started
                {
                    NotifyCompleted();
                }
            }
        }
        protected void ProcessDownloadedData()
        {
            Boolean errorOccured = false;
            Exception error = null;
            ErrorCause cause = ErrorCause.NO_ERROR;
            try
            {
                var temporaryList = new List<DataItem>();
                switch (Settings.Current.CurrentEngine.GetEntriesFromHtmlDocument(primaryWebClientResult, temporaryList,out error, out cause))
                {
                    case ScheduleParseResult.NO_TRANSITION:
                        errorOccured = true;
                        cause = ErrorCause.NO_TRANSITION;
                        break;
                    case ScheduleParseResult.FAILURE:
                        errorOccured = true;
                        break;
                    case ScheduleParseResult.ERRORS:
                    case ScheduleParseResult.COMPLETE:
                        cache.Value = temporaryList;
                        cache.Sync();
                        cache.Dump();
                        break;
                }                
            }
            catch (Exception e)
            {
                NotifyError("An unknown error has occured during document parsing", e, ErrorCause.PARSE_ERROR);
                errorOccured = true;
            }
            finally
            {
                busy = false;
                CheckReleaseEvent();
            }
            if (!errorOccured) NotifyCompleted();
            else NotifyError("Known error has occured", error, cause);
        }

        protected Boolean CacheExpired()
        {
            return cache.DataAcquireTime.Date < DateTime.Today;
        }
        private void CheckReleaseEvent()
        {
            if (sync_mode)
            {
                synchronizeLock.Set();
                sync_mode = false;
            }
        }
        protected virtual void NotifyCompleted()
        { }
        protected virtual void NotifyError(String Text, Exception e, ErrorCause cause)
        { }
        protected virtual void NotifyInvoked()
        { }
        #endregion
        public DataAccess(RouteEntry favourite)
        {
            parentFavourite = favourite;
            ///
            primaryWebClientResult = null;
            ///
            cache = new SyncableXml<List<DataItem>>(parentFavourite.startStation.id.ToString() + "-" + parentFavourite.endStation.id.ToString() + ".cache.xml");
        }
    }
    public enum DataSource
    {
        WEB,
        CACHE,
        DATABASE,
        NONE
    }
    public enum ErrorCause
    {
        NO_CONNECTION,
        PARSE_ERROR,
        DOWNLOAD_ERROR,
        NO_ERROR,
        NO_TRANSITION
    }
}
